package org.gracefulwebsocket;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.*;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;

/**
 * Simple chat service that supports both push and poll for messages.
 * Message polling is implemented using time frame between previous poll request 
 * and current poll request
 * 
 * @author david.lindkvist
 *
 */
public class ChatServiceServlet extends WebSocketServlet implements Servlet 
{
	private static final long serialVersionUID = 2010031501L;
	private static final int BUFFER_SIZE = 100;	
	
	// these are shared resources - needs to be thread safe!
	protected Queue<ChatMessage> echoBufferFIFO = new ConcurrentLinkedQueue<ChatMessage>(); 
	protected final Set<ChatWebSocket> clients = new CopyOnWriteArraySet<ChatWebSocket>();

	protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException
	{
		long previousRequest = getRequestTimestamp(req, "previousRequest");
		long currentRequest =  getRequestTimestamp(req, "currentRequest");		
		res.setContentType("text/html;charset=utf-8");
		res.getWriter().write( getMessagesAsJSON(previousRequest, currentRequest) );
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException
	{ 
		// read POST body and add to message queue
		newMessage(req.getReader().readLine());

		// GET latest messages
		doGet(req, res);
	}

	protected WebSocket doWebSocketConnect(HttpServletRequest req, String arg) 
	{
		Log.info("creating new WebSocket");
		return new ChatWebSocket();
	}
	
	class ChatWebSocket implements WebSocket
    {
        public Outbound outbound;

        public void onConnect(Outbound outbound)
        {
            this.outbound = outbound;
            clients.add(this);
        }
        
        public void onDisconnect()
        {
            clients.remove(this);
        }
        
        public void onMessage(byte frame, String data)
        {
        	newMessage(data);
        }
        
        public void onMessage(byte frame, byte[] data, int offset, int length) {}
    }
	
	public class ChatMessage 
	{
		public long timestamp;
		public String text;
		
		public ChatMessage(String text)
		{
			this.timestamp = System.currentTimeMillis();
			this.text = text;
		}
		
		public JSONObject toJSONObject()
		{
			JSONObject json = new JSONObject();
			try 
			{
				json.append("text", text);
				json.append("messageID", timestamp);
			} 
			catch (JSONException e) {}
			return json;
		}
	}	

	
	protected void newMessage(String data)
	{		
		// add new echo to buffer
		ChatMessage msg = new ChatMessage(data);
		echoBufferFIFO.add(msg);
		
		// enforce length limit on buffer
    	if(echoBufferFIFO.size() > BUFFER_SIZE)
    		echoBufferFIFO.poll();
    	
    	// push message to all WebSocket clients
        for (ChatWebSocket socket : clients)
        {
            try
            {
                socket.outbound.sendMessage((byte)0, msg.toJSONObject().toString());
            }
            catch(IOException e) {Log.warn(e);}
        }
	}
	
	
	protected String getMessagesAsJSON(long minTimestamp, long maxTimestamp) 
	{
		JSONArray msgs = new JSONArray();
		for(ChatMessage msg : echoBufferFIFO)
		{
			if(minTimestamp > -1 
					&& msg.timestamp > minTimestamp 
					&& msg.timestamp <= maxTimestamp) {
				msgs.put(msg.toJSONObject());
			}
		}
		return msgs.toString();
	}
	
	
	
	protected long getRequestTimestamp(HttpServletRequest req, String parameterName)
	{
		String parameter = getQueryMap(req.getQueryString()).get(parameterName);
		
		long timestamp = -1;
		try 
		{ 
			timestamp = Long.parseLong(parameter); 
		}
		catch(NumberFormatException e) {};
		
		return timestamp;
	}
	
	protected Map<String, String> getQueryMap(String queryString)  
	{  
	    Map<String, String> map = new HashMap<String, String>();
		
	    if(queryString == null)
	    	return map;
	    
	    String[] params = queryString.split("&");    
	    for (String param : params)  
	    {  
	    	String name = param.split("=")[0];  
	    	String value = param.split("=")[1];
	    	map.put(name, value);  
	    }  
	    return map;  
	}  

}